package dawn.jxufe.community.config;

import dawn.jxufe.community.util.CommunityConstant;
import dawn.jxufe.community.util.CommunityUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author dawn21
 * @version 1.0
 * @date 2022/8/25 11:11
 * @component 思路: 先导入依赖，然后进行spring security 进行配置。
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 忽略对静态资源的拦截。
        // 所有resources之下的所有静态资源都可以直接访问。
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 授权。查看controller中的方法，哪些是需要授权才可以访问的，哪些是不需要授权就可以访问的。
        // 路径和方法名要注意。10:00
        // 对于这些路径，只要拥有以下任意一个权限，就可以访问。
        // 首页-直访、user头像和主页-直访、
        http.authorizeRequests()
                .antMatchers(
                        "/user/setting",
                        "/user/upload",
                        "/discuss/add",
                        "/comment/add/**",
                        "/letter/**",
                        "/notice/**",
                        "/like",
                        "/follow",
                        "/unfollow"
                )
                .hasAnyAuthority(
                        AUTHORITY_USER,
                        AUTHORITY_ADMIN,
                        AUTHORITY_MODERATOR
                )
                .antMatchers(
                        "/discuss/top",
                        "/discuss/wonderful"
                )
                .hasAnyAuthority(
                        AUTHORITY_MODERATOR
                )
                .antMatchers(
                        "/discuss/delete",
                        "/actuator/**"
                )
                .hasAnyAuthority(
                        AUTHORITY_ADMIN
                )
                .anyRequest().permitAll()
                .and().csrf().disable(); // 除了这些请求之外的所有请求都允许直接访问。// 不防御CSRF攻击。

        // 权限不够时的处理。
        http.exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() { // 没有登录时发出了请求时的处理，没有登录认证，需要认证。
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        // 考虑请求的方式，直接请求则跳转到首页，异步请求则返回json(提示)。
                        // 如何判断是什么请求方式：看请求消息头的一个字符串的值。
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) { // 异步
                            response.setContentType("application/plain;charset=utf-8");
                            PrintWriter writer = response.getWriter();
                            writer.write(CommunityUtil.getJSONString(403, "你还没有登录!"));
                        } else {
                            // 直接重定向到登录页面controller。
                            response.sendRedirect(request.getContextPath() + "/login");
                        }
                    }
                })
                .accessDeniedHandler(new AccessDeniedHandler() { // 登录了发送了请求时权限不足时的处理。
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        // 考虑请求的方式，直接请求则跳转到首页，异步请求则返回json(提示)。
                        // 如何判断是什么请求方式: 看请求消息头的一个字符串的值。
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) { // 异步
                            response.setContentType("application/plain;charset=utf-8");
                            PrintWriter writer = response.getWriter();
                            writer.write(CommunityUtil.getJSONString(403, "你没有访问此功能的权限!"));
                        } else {
                            // 重定向到另一个controller。
                            // 其实也就是将另一个controller发给浏览器，然后浏览器重新访问那个controller。
                            response.sendRedirect(request.getContextPath() + "/denied");
                        }
                    }
                });

        // spring security的Filter会自动拦截名字为logout的请求，进行退出的处理，
        // 如果它拦截了，则我们自己写的logout就不会执行了。为了执行我们自己的logout，覆盖它的逻辑就行。
        // 改变logout的名字，它就不会拦截logout请求了，就会执行我们自己的logout了。
        http.logout().logoutUrl("/securitylogout");

        // 授权是授予权限，登录才是登录认证。
        // 认证:认证信息->封装到token->Filter->security context->授权:从context得到token，通过token来判断权限。
        // 绕过它的认证，使用我们自己的认证，然后将认证信息封装给security context。
    }
}
